//-----------------------------升级版promise，研究then中返回的值
/* 
promise只有规范，没有标准的promise，只要自己的写的符合这个规范就是promise。promise aplus网站有实现规范和测试自己写出来的promise实现是否符合要求的插件，可以自己写一下试试。

如果一个promise的then中的函数（成功和失败）返回的结果是普通值，则直接传给下一个then作为成功函数的参数；如果返回结果是一个promise的话，会自动将这个promise执行，并且采用它的状态，如果成功会将成功的结果向外层的下一个then传递。

每次执行promise的then方法时，都会new一下自己，返还一个新的promise实例；

对then中传递的函数判断，判断成功和失败函数的返回结果：
1、判断是不是promise，如果是promise就采用它的状态；
2、如果不是promise，直接将结果传递下去即可（传给后面的then中的成功函数作为参数）
注意：
    let p = new Promise((resolve,reject) => {
        throw new Error('错误');
    });

    p.then(res => {
        console.log('then-1-success');
        console.log(res);
    },err => {
        console.log('then-1-failed');
    }).then(res => {
        console.log('then-2-success');
        console.log(res);
    },err => {
        console.log('then-2-failed');
    });

    //最后结果打印：then-1-failed  then-2-success undefined，想要说的是不要以为promise的第一个then方法执行了失败函数，后面的then都是执行失败函数；后面每个then会执行成功还是失败函数，取决于前一个then的返回值（有3种情况）：
    1、返回普通值就是执行成功函数；
    2、返回promise就采用返回的promise状态；
    3、如果直接抛错，就是直接执行失败函数；

下面是对基础版promise的升级，可以进行then的链式操作。
 */
//宏变量
const PENDING = 'PENDING';
const RESOLVE = 'RESOLVE';
const REJECT = 'REJECT';

/* 
resolvePromise说明：
因为所有的promise都遵循这个规范，规定这里的这个resolvePromise应该要兼容所有的promise.
const mypromise = require("./mypromise");

let p = new mypromise((resolve,reject) => {
    resolve(1);
});

p.then(res => {
    //这里可能要用到其他的promise库或框架中的promise，我们需要做到兼容。
    return new Promise((resolve,reject) => {
        resolve(2);
    });
},err => {
}); */

const resolvePromise = (promise2,x,resolve,reject) => {
    /* 
    判断x的值，promise2和x如果指向同一个对象，不用等待了，执行失败态，说明如下：
    let p = new Promise((resolve,reject) => {
        resolve(1);
    });
    
    let promise2 = p.then(res => {
        //这里的promise2就是x
        return promise2;  //这里返回一个promise，promise2将采用这个promise返回的状态，但是这个promise既没有执行成功也没有执行失败，就循环引用了；举个例子，我在等着我买东西回来。
    }); 
    */ 
    if(promise2 === x){
        return reject(new TypeError("Chaining cycle detected for promise #<Promise>"));
    }
    //判断数据类型 typeof constructor intanceof toSting
    if(typeof x === "object" && x!==null || typeof x === "function"){
        let called;//调用成功或失败的标志，只让调用一次
        try{//使用捕错，以防获取x.then报错，万一别人写的库then属性是通过defineProperty定义getter，里面写个抛错呢
            let then = x.then;
            if(typeof then === "function"){//，姑且认为它就是promise
                then.call(x,y =>{
                    if(called){
                        return
                    }
                    called = true;
                    //y可能还是一个promise，直到解析出的结果为一个普通值为止
                    resolvePromise(promise2,y,resolve,reject);//采用promise的成功结果将值向下传递
                }, r =>{
                    if(called){
                        return
                    }
                    called = true;
                    reject(r);//采用失败的结果向下传递
                });
            }else{
                //有then属性的普通对象，例如：{then:1}
                resolve(x); //直接让promise2成功即可   
            }
        }catch(e){
            if(called){
                return
            }
            called = true;
            reject(e);
        }
    }else{
        //x是一个普通值
        resolve(x); //直接让promise2成功即可
    }
}

class Promise {
    //1、看这个属性能否在原型上使用
    //2、看属性是否公用
    constructor(executor){
        this.status = PENDING;//初始状态默认是pending状态
        this.value = undefined;//成功的值
        this.reason = undefined;//失败的原因
        this.onfulfilledCallbacks = [];
        this.onrejectedCallbacks = [];

        //成功函数
        let resolve = (value) => {
            if(this.status === PENDING){
                this.status = RESOLVE;
                this.value = value;
                this.onfulfilledCallbacks.forEach(fn => fn());
            }
        }

        //失败函数
        let reject = (reason) => {
            if(this.status === PENDING){
                this.status = REJECT;
                this.reason = reason;
                this.onrejectedCallbacks.forEach(fn => fn());
            }
        }

        //这里只是同步报错的捕获，没有处理异步报错。(貌似原生的也没有处理异步的报错)
        try {
            executor(resolve,reject);//实例化的时候，默认执行器会立即执行
        }catch(e){
            reject(e);//如果执行时发生错误，等价于调用了失败方法
        }
    }
    then(onfulfilled,onrejected){
        //为了then函数无参数的链式调用，如果是成功，则成功的值一直向下传递，如果是失败，则错误一直向外抛出，如p.then().then().then(res=>{},err=>{});
        onfulfilled = typeof onfulfilled == "function" ? onfulfilled:data => data;
        onrejected = typeof onfulfilled == "function" ? onrejected:err => {
            throw err;//不能写成return throw err;所以要加花括号
        };
        /* 先初始化一个promise，然后在赋值给promise2，然而初始化promise的时候执行器会立即执行；执行到resolvePromise的时候，需要传入promise2，但这时promise2还未创建；所以会报错，因此需要把调用resolvePromise改为异步，如下面的代码套上了setTimeout，注意，即使setTimeout的时间是0，不要认为它是同步的，setTimeout会把它内部的函数加入异步队列，只要同步代码都执行完了，才会去查看异步队列，再加异步队列中的任务拿到主进程中执行。 mdn中说明，即使setTimeout的时间设置为0，但其实最小延迟时间>=4ms。*/
        let promise2 = new Promise((resolve,reject) => {
            //同步处理
            if(this.status === RESOLVE){
                //改为异步是因为resolvePromise传入promise2时，promise2还未创建；
                setTimeout(() => {//宏任务 为了保证promise2已经new完了
                    /* 这里加上捕错是因为上面代码中执行executor的捕错是同步的，这里是异步的，所以需要加上捕错，应对的是then函数中抛出错误，如在then函数中直接throw new Error();这时还没去判断then中的返回值，就直接执行reject即可。 */
                    try{
                        let x = onfulfilled(this.value);// x 就是then函数中的返回值
                        resolvePromise(promise2,x,resolve,reject);
                    }catch(e){
                        reject(e);
                    }
                },0);
            }

            //同步处理
            if(this.status === REJECT){
                setTimeout(() => {
                    try{
                        let x = onrejected(this.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    }catch(e){
                        reject(e);
                    }
                },0);
            }

            //为解决异步（用到了发布订阅思想），例如实例化promise时执行器中用了setTimeout延迟3s后再调用resolve。
            if(this.status === PENDING){
                //如果是异步，就先订阅
                this.onfulfilledCallbacks.push(() => {
                    //这里还能自定义做点啥，有可扩展性
                    setTimeout(() => {
                        try{
                            let x = onfulfilled(this.value);
                            resolvePromise(promise2,x,resolve,reject);
                        }catch(e){
                            reject(e);
                        }
                    },0);
                });

                this.onrejectedCallbacks.push(() => {
                    //这里还能自定义做点啥，有可扩展性
                    setTimeout(() => {
                        try{
                            let x = onrejected(this.reason);
                            resolvePromise(promise2,x,resolve,reject);
                        }catch(e){
                            reject(e);
                        }
                    },0);
                });
            }
        });
        return promise2;
    }
}

//给Promise增加延迟对象defer(别名deferred)，原生是不支持的，不是标准里的，但是很多库都实现了，自己写的promise可以实现这一点，在调用时可以少一层嵌套；
Promise.defer = Promise.deferred = function(){
    let dfd = {};
    dfd.promise = new Promise((resolve,reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}

module.exports = Promise;